类层次结构
这里的问题在于:没有一种方式来区分所有形状的共有特征(例如,一个形状总有一种颜色,可以被画出来等),与某个特定形状种类的特殊特征(如圆是一种形状,它有一个半径,需要用画圆的函数画出等)。表达这种区分并由此获益就定义了面向对象的程序设计。具有能表达和利用这种区分的结构的语言将能支持面向对象的程序设计,其他语言就不行。
继承机制(是C++从Simula借来的)提供了一种解决方案。首先,我们描述一个定义了所有形状的共同特征的类:
class Shape {
Point center;
Color col;
// ...
public:
Point where() { return center; }
void move(Point to) { center = to; /* ... */ draw(); }
virtual void draw() = 0;
virtual void rotate(int angle) = 0;
// ...
};
就像在2.5.4节中的抽象类型 Stack,所有提供了被定义的调用界面---但其实现却不能定义---的函数都是 virtual
。特别地,函数draw()
和 rotate()
只能针对特定形状的语义去定义,所以它们被声明为 virtual
。
在给出这个定义之后,我们就可以写出函数,实现某种针对指向形状的指针向量的操作:
void rotate_all(vector<Shape*>& v, int angle) // 将v的所有元素旋转angle度
{
for(int i = 0; i < v.size(); i++) v[i]->rotate(angle);
}
要定义出一个特殊的形状,我们必须说明它是一个形状,并描述它的特殊性质(包括那些虚函数):
class Circle : public Shape
{
int radius;
public:
void draw() { /* ... */ }
void rotate(int) {} // 的确,这是个空函数
};
在 C++ 里,类 Circle 被称为是由类 Shape 派生的,而类 Shape 被称为是类 Circle 的基类。关于 Circle 和 Shape 的另一对术语是子类和超类。我们还说派生类由其基类继承了成员,所以,对基类和派生类的使用通常也被说成是继承。
现在程序设计范型是:
确定你需要哪些类;为每个类提供完整的一组操作;利用继承去明确地表示共性。
在不存在共性的地方,数据抽象就足够了。在类型之间,能够通过使用继承和虚函数挖掘出的共性的数量,可以看做是面向对象程序设计对于该问题的适用性的一个石蕊检验。在一些领域中,例如交互式图形,存在着面向对象程序设计的广阔空间。而在另一些领域,例如经典的算术类型和基于它们的计算领域,看来就很少有什么地方需要数据抽象之外的东西了,为支持面向对象程序设计而提供的功能在这里就未必是必需的。
在一个系统中找出共性并不是一件不值一提的事情。能够利用的共性的量也受到系统设计方式的影响。在设计系统的时候---甚至是在写有关系统的需求时---就应该主动地去考虑共性问题。可以特别地将类设计为构造其他类型的基本构件。现存的类也应该检查,看看它们是否表现出某些共性,能够通过一个基类加以利用。
要想去弄清楚什么是面向对象的程序设计,而又不依赖于特定的程序设计语言结构,可以参看23.6节中给出的[Kerr,1987]和[Booch, 1994]。
类层次结构和抽象类(2.5.4节)互为补充而不是互相排斥(12.5节)。一般来说,列在这里的所有范型都是互相补充的,常常是互相支持的。例如,类和模块都包含着函数,而模块又包含着类和函数。有经验的程序员会根据需要去选择使用各种不同的范型。
🔚